﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;

using BizElements.Generator.Properties;

namespace BizElements.Generator
{
    public partial class GeneratorCtl : UserControl
    {
        #region Fields.

        ICodeGenerator codeGenerator;
        TraceQueue traceQueue;        

        #endregion

        public GeneratorCtl()
        {
            InitializeComponent();
        }

        private void GeneratorCtl_Load(object sender, EventArgs e)
        {
            if (!DesignMode)
            {
                if (string.IsNullOrEmpty(this.uiSelectedOutputFolder.Text))
                    this.uiSelectedOutputFolder.Text = GetDefaultOutputFolder();
            }
        }

        #region Properties, delegates, events.

        public string OutputDirectory
        {
            get { return this.uiSelectedOutputFolder.Text; }
        }

        public event EventHandler GenerationStarted;
        public event EventHandler GenerationStopped;

        [Browsable(false)]
        public CodeGeneratorBuilderMethod BuildGenerator { get; set; }

        [Browsable(false)]
        public CodeGeneratorPropertiesBuilderMethod BuildGeneratorProperties { get; set; }

        #endregion

        private void uiChooseOutputFolder_Click(object sender, EventArgs e)
        {
            var answer = this.uiOutputFolderBrowser.ShowDialog();
            if (answer == DialogResult.OK)
            {
                this.uiSelectedOutputFolder.Text = this.uiOutputFolderBrowser.SelectedPath;
            }
        }

        #region Start generator.

        private void uiStart_Click(object sender, EventArgs e)
        {
            StartCodeGeneration();
        }

        private void StartCodeGeneration()
        {
            ICodeGeneratorProperties properties = TryCreateAndValidateGeneratorProperties();
            if (properties == null)
                return;

            CreateAndAttachToGenerator(properties);
            ResetTraceGui();
            SwitchState(true);
            try
            {
                // Start generator in another thread so that it can be stopped.				
                Thread generationThread = new Thread(new ThreadStart(this.codeGenerator.GenerateCode));
                generationThread.Start();
            }
            catch
            {
                FinishGeneration();
            }
        }        

        private ICodeGeneratorProperties TryCreateAndValidateGeneratorProperties()
        {
            ICodeGeneratorProperties properties = BuildGeneratorProperties();
            properties.OutputFolder = this.OutputDirectory;
            string[] errors = properties.Validate();
            if (errors.Length > 0)
            {
                MessageBox.Show(string.Join(Environment.NewLine, errors), Messages.Error, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return null;
            }
            else
            {
                return properties;
            }
        }

        private void CreateAndAttachToGenerator(ICodeGeneratorProperties properties)
        {
            this.codeGenerator = BuildGenerator(properties);
            this.codeGenerator.ItemGenerated += new EventHandler<EventArgs<GenerationTraceItem>>(this.HandleGeneratorTraceOutput);
            this.codeGenerator.GenerationCompleted += new EventHandler<EventArgs<GenerationTraceItem>>(this.HandleGenerationCompleted);
        }

        #endregion

        private void SwitchState(bool isGenerating)
        {
            this.uiStop.Enabled = isGenerating;
            this.uiStart.Enabled = !isGenerating;
            this.uiChooseOutputFolder.Enabled = !isGenerating;
            
            if (isGenerating && this.GenerationStarted != null)
                GenerationStarted(this, null);

            if (!isGenerating && this.GenerationStopped != null)
                GenerationStopped(this, null);
        }

        private void uiStop_Click(object sender, EventArgs e)
        {
            if (this.codeGenerator != null)
            {
                this.codeGenerator.StopGenerator();
                SwitchState(false);
            }
        }

        private void FinishGeneration()
        {
            this.traceQueue.StopProcessing();
            SwitchState(false);            
        }

        #region Trace.

        private void ResetTraceGui()
        {
            this.uiProgressBar.Minimum = 0;
            this.uiProgressBar.Maximum = 100;
            this.uiTrace.Text = "";
            this.uiProgressBar.Value = 0;
            Application.DoEvents();
            this.traceQueue = new TraceQueue(this.uiTrace, this.uiProgressBar);
            this.traceQueue.StartProcessing();            
        }

        private void HandleGenerationCompleted(object sender, EventArgs<GenerationTraceItem> ea)
        {
            this.traceQueue.AddItem(ea.Item.Message, ea.Item.ProgressPercentage);
            FinishGeneration();
        }

        private void HandleGeneratorTraceOutput(object sender, EventArgs<GenerationTraceItem> ea)
        {
            // Output is handled in async thread to ensure that generator is not slowed down due to GUI procesing.
            this.traceQueue.AddItem(ea.Item.Message, ea.Item.ProgressPercentage);
        }

        /// <summary>Traces generation progress in an async queue.</summary>
        sealed class TraceQueue
        {
            #region Fields.

            TextBox uiTrace;
            ProgressBar uiProgressBar;
            bool isTraceFinished;
            Queue<TraceItem> queue = new Queue<TraceItem>();
            object dequeueLock = new object();
            const int RefreshIntervalMs = 50;

            #endregion

            #region CTor.

            public TraceQueue(TextBox uiTrace, ProgressBar uiProgressBar)
            {
                this.uiTrace = uiTrace;
                this.uiProgressBar = uiProgressBar;
            }

            #endregion

            #region Methods.

            public void StartProcessing()
            {
                Thread displayThread = new Thread(new ThreadStart(this.LoopUntilFinished));
                displayThread.Start();
            }

            public void StopProcessing()
            {
                this.isTraceFinished = true;
            }

            private void LoopUntilFinished()
            {
                // Running in dedicated display thread.
                while (!this.isTraceFinished)
                {
                    if (this.queue.Count == 0)
                    {
                        Thread.Sleep(RefreshIntervalMs);
                    }
                    else
                    {
                        // Catch-up.
                        var item = DequeueAllItems();
                        if (item != null)
                            DisplayTraceItem(item.Message, item.Percentage);
                    }
                }

                // Display all remaining items.
                Thread.Sleep(RefreshIntervalMs);
                var remainingItems = DequeueAllItems();
                if (remainingItems != null)
                    DisplayTraceItem(remainingItems.Message, remainingItems.Percentage);
            }

            private TraceItem DequeueAllItems()
            {
                TraceItem concatenatedItem = null;
                StringBuilder concatenatedMessage = new StringBuilder();
                lock (this.dequeueLock)
                {
                    TraceItem lastItem = null;
                    if (this.queue.Count > 0)
                    {
                        while (this.queue.Count > 0)
                        {
                            lastItem = this.queue.Dequeue();
                            concatenatedMessage.AppendLine(lastItem.Message);
                        }

                        concatenatedItem = new TraceItem() { Message = concatenatedMessage.ToString(), Percentage = lastItem.Percentage };
                    }
                }

                return concatenatedItem;
            }

            private void DisplayTraceItem(string message, int percentage)
            {
                this.uiTrace.AppendText(message);
                if (percentage > this.uiProgressBar.Value)
                    this.uiProgressBar.Value = percentage;
            }

            public void AddItem(string message, int percentage)
            {
                var item = new TraceItem() { Message = message, Percentage = percentage };
                lock (this.dequeueLock)
                {
                    this.queue.Enqueue(item);
                }
            }

            #endregion

            sealed class TraceItem
            {
                public string Message;
                public int Percentage;
            }
        }                

        #endregion

        #region Settings.

        public void ReadSettings(BizElementsSettings settings)
        {
            this.uiSelectedOutputFolder.Text = settings["Generator_OutputFolder"] ?? GetDefaultOutputFolder();
        }

        public void WriteSettings(BizElementsSettings settings)
        {
            settings["Generator_OutputFolder"] = this.uiSelectedOutputFolder.Text.Trim();
        }

        private static string GetDefaultOutputFolder()
        {
            string cfgValue = Settings.Default.GeneratorCtl_DefaultOutputFolder;
            bool isRelative = cfgValue.StartsWith(".");
            if (isRelative)
                return IoUtil.GetExecutableDirectory() + cfgValue.Substring(1);
            else
                return cfgValue;
        }

        #endregion       
    }
}
